/**
 * 
 */
package compress;

import huffmantree.*;

import inOut.OutputBitStream;
import inOut.Read;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;


/**
 * @author Groupe 2
 * 
 */
public class Compress {
	
	public static final boolean BIT_1 = true;
	public static final boolean BIT_0 = false;

	public static void main(String[] args) throws IOException
	{
		
        if(args.length != 2)
        {
                System.err.println(args.length+" arguments. Il faut 2 noms fichiers pour que le programme fonctionne.");
                System.exit(1);
        }
        
        String text = new String();
        
        Read.openReader(args[0]);
        text = Read.readInstruction();
        String line;
        while((line = Read.readInstruction()) != null)
        {
        	text = text.concat(line);
        }
        Read.closeReader();
        
        writeFile(text, freqCalc(text), buildTree(freqCalc(text)), args[1]);
	}

	// @author Tristan Carion, Julie Chatelain, Darko Nikolas
	public static Map<Character, Integer> freqCalc(String text) 
	{
		Map<Character, Integer> freqmap = new HashMap<Character, Integer>();

		for (char c : text.toCharArray()) {
			if (freqmap.containsKey((Character) c)) {
				freqmap.put((Character) c, (Integer) (freqmap.get((Character) c))+1);
			} else {
				freqmap.put((Character) c, (Integer) 1);
			}
		}
		return freqmap;
	}

	// @author Olivier Dekeyser, Ludovic Fastré
	public static Tree buildTree(Map<Character, Integer> map) {
		PriorityQueue<Tree> prior = new PriorityQueue<Tree>();

		for (char character : map.keySet()) {
			if (map.get(character) > 0) {
				prior.offer(new Leaf(map.get(character), character));
			}
		}

		while (prior.size() > 1) {
			Tree left = prior.poll();
			Tree right = prior.poll();

			prior.offer(new Node(left, right));
		}

		return prior.poll();
	}
	
	//A changer byte par bit. Et enregistrer l'arbre avec la technique de Ludo.
	// @author Ludovic Fastré
	public static void writeFile(String text, Map<Character, Integer> map, Tree tree, String path) throws FileNotFoundException
	{
		OutputBitStream obs;
		try {
			obs = new OutputBitStream(path);

			for (char character : map.keySet()) {
				if (map.get(character) > 0) {
					obs.write(character);
					obs.write(':');
					boolean b = (map.get(character)!=0) ? BIT_1 : BIT_0;
					obs.write(b);
				}
			}
			obs.write(';');
			
			// Map des caractères et des bytes correspondants
			List<Integer> integerList = new ArrayList<Integer>();
			Map<Character, List<Integer>> huffmanMap = huffmanMap(tree, integerList);
			char[] textByChar = text.toCharArray();
			for(int i = 0 ; i<textByChar.length ; i++){
				for(int charToBit : huffmanMap.get(textByChar[i])){
					boolean b = (charToBit!=0) ? BIT_1 : BIT_0;
					obs.write(b);
				}
			}
			obs.close();
		} catch (IOException e1) {
			System.out.println("Problème lors de l'écriture de compression");
			e1.printStackTrace();
		}
	}

	// @author Olivier Dekeyser, Ludovic Fastré
	private static Map<Character, List<Integer>> huffmanMap(Tree tree, List<Integer> byteList) {
		Map<Character, List<Integer>> map = new HashMap<Character, List<Integer>>();

		if (tree instanceof Leaf) {
			Leaf leaf = (Leaf) tree;
			map.put(leaf.value, byteList);

		} else if (tree instanceof Node) {
			Node node = (Node) tree;

			// traverse left
			byteList.add(0);
			huffmanMap(node.left, byteList);
			byteList.remove(byteList.size() - 1);

			// traverse right
			byteList.add(1);
			huffmanMap(node.right, byteList);
			byteList.remove(byteList.size() - 1);
		}
		return map;
	}
}
